package hxm;

import haxe.macro.Expr;
import haxe.macro.Context;
import hxm.core.Initializer;

import tink.macro.tools.MacroTools;

import hxm.core.Parser;
import hxm.core.Typer;
import hxm.core.Generator;
import hxm.core.Resolver;
import hxm.monads.OptionMonad;

/**
 * @author Simon Krajewski
 * 
 * The Monad class provides an exec method which takes a block of expressions as argument. These expressions are
 * translated into nested functions that match the monadic structure.
 * Example:
	 * var result = Monad.exec({
	 *     x <= [1, 2, 3]; // bind the array [1, 2, 3] to x
	 *     y <= [1, 2, 3]; // bind the array [1, 2, 3] to y
	 *     return x * y;   // will return [1, 2, 3, 2, 4, 6, 3, 6, 9]
	 * });
 */
class Monad 
{
	/**
	 * Executes a block of expressions as monadic operations. The following formats
	 * are recognized:
	 * identifier <= expr: // Binds expr to identifier. If expr is of EBlock, it is treated as an inner monadic scope.
	 * expr;               // Executes expr as normal expression
	 * return expr;        // Yields expr
	 * @param	exprs the expressions
	 */
	@:macro static public function exec(expr:Expr)
	{
		Initializer.init();
		var exprs = switch(expr.expr)
		{
			case EBlock(a):
				var ret = [];
				for (i in a)
					ret.push(i);
				ret;
			default:
				Context.error("Block expected.", Context.currentPos());
		}
				
		var mexpr = Parser.parseExpr(exprs);
		var tmexpr = Typer.typeMExpr(mexpr, {type:None, idents:new Hash<Dynamic>()});
		var expr = Generator.generateExpr(tmexpr);
		
		return expr;
	}
	

	/**
	 * Adds a path to to list of paths that is checked for monadic classes.
	 * @param	path the dot-separated path
	 */
	@:macro static public function addMonadPath(path:String)
	{
		Resolver.addPath(path);
		return {expr:EBlock([]), pos:Context.currentPos() };
	}
	
	/**
	 * Adds an alias for a given type. Example: IntIter is aliased to Iterator by default so the
	 * monadic class can be correctly resolved.
	 * @param alias the alias
	 * @param to the alias' target
	 */
	@:macro static public function addAlias(alias:String, to:String)
	{
		Resolver.defineAlias(alias, to);
		return {expr:EBlock([]), pos:Context.currentPos() };
	}
}